/*
 *  XIO_Controller.cpp
 *  initiativeTracker
 *
 *  Created by Sam Jaffe on 5/10/12.
 *  Copyright 2012 WPI. All rights reserved.
 *
 */

#include <regex>
#include <sstream>
#include <iostream>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <xercesc/dom/DOMNode.hpp>
#include <xercesc/dom/DOMNamedNodeMap.hpp>
#include <xercesc/dom/DOMNodeList.hpp>
#include "CharGen.h"
#include "XIOController.h"

#pragma mark Constants

namespace {
    const std::string success = "<relayResponse/></response>";
    const std::regex _request("<request.*>|</request>");
    const std::regex _response("<repsonse.*>|</response>");
    const int ELEMENT_NODE = xercesc::DOMNode::ELEMENT_NODE;
}

#pragma mark General Handler methods

void process_changeTurn(std::shared_ptr<InitTracker> init, Message* xml) {
    xercesc::DOMNode* base = xml->contents()->getFirstChild();
    rotate(base);
    
    xercesc::DOMNode* dot_node = base->getAttributes()->getNamedItem((XMLCh*) "dot");
    
    if (dot_node != 0)
        init->get(init->nextTurn())->setDot(atoi((char*)dot_node->getNodeValue()));
    
    init->nextTurn();
}

void process_createUnit(std::shared_ptr<InitTracker> init, Message* xml) {
    xercesc::DOMNode* base = xml->contents()->getFirstChild();
    rotate(base);
    
    xercesc::DOMNode* point = base->getFirstChild();
    xercesc::DOMNode* end = base->getLastChild();
    
    for (; point <= end; point++) {
        rotate(point);
        if (point->getNodeType() == ELEMENT_NODE)
            init->decode(point);
    }
}

void process_damageUnit(std::shared_ptr<InitTracker> init, Message* xml) {
    xercesc::DOMNode* base = xml->contents()->getFirstChild();
    rotate(base);
    
    xercesc::DOMNode* point = base->getFirstChild();
    xercesc::DOMNode* end = base->getLastChild();
    
    int count;
    
    for (; point <= end; point++) {
        rotate(point);
        xercesc::DOMNamedNodeMap* map = point->getAttributes();
        
        int id = atoi((char*)map->getNamedItem((XMLCh*)"id")->getNodeValue());
        int dmg = atoi((char*)map->getNamedItem((XMLCh*)"value")->getNodeValue());
        xercesc::DOMNode* tempNode = map->getNamedItem((XMLCh*)"temp");
        bool temp = false;
        
        if (tempNode != 0)
            temp = (((char*) tempNode->getNodeValue()) == "true");
        else if (init->damage(id, dmg, temp))
            count++;
    }       
}

void process_delayTurn(std::shared_ptr<InitTracker> init, Message* xml) {
    init->delay();
}

void process_killUnit(std::shared_ptr<InitTracker> init, Message* xml) {
    xercesc::DOMNode* base = xml->contents()->getFirstChild();
    rotate(base);
    
    xercesc::DOMNode* point = base->getFirstChild();
    xercesc::DOMNode* end = base->getLastChild();
    
    int count;
    
    for (; point <= end; point++) {
        rotate(point);
        xercesc::DOMNamedNodeMap* map = point->getAttributes();
        
        int id = atoi((char*)map->getNamedItem((XMLCh*)"id")->getNodeValue());
        
        init->kill(id);
    }        
}

void process_killAllUnits(std::shared_ptr<InitTracker> init, Message* xml) {
    xercesc::DOMNode* base = xml->contents()->getFirstChild();
    rotate(base);

    bool all = (((char*) base->getAttributes()->getNamedItem((XMLCh*) "all")->getNodeValue())
                == "true");
    
    init->clear(all);
}

void process_nullInitiative(std::shared_ptr<InitTracker> init, Message* xml) {
    int i;
    for (i = 0; i < init->size(); i++)
        init->get(i)->nullInit();
}

void process_rollInitiative(std::shared_ptr<InitTracker> init, Message* xml) {
    xercesc::DOMNode* base = xml->contents()->getFirstChild();
    rotate(base);

    xercesc::DOMNode* point = base->getFirstChild();
    xercesc::DOMNode* end = base->getLastChild();
    
    for (; point <= end; point++) {
        rotate(point);
        xercesc::DOMNamedNodeMap* map = point->getAttributes();
        
        int id = atoi((char*) map->getNamedItem((XMLCh*) "id")->getNodeValue());
        int roll = atoi((char*) map->getNamedItem((XMLCh*) "value")->getNodeValue());
        
        init->lookup(id)->setInit(roll);
    }
}

void process_healingSurge(std::shared_ptr<InitTracker> init, Message* xml) {
    xercesc::DOMNode* base = xml->contents()->getFirstChild();
    rotate(base);

    xercesc::DOMNode* surge = base->getFirstChild();
    rotate(surge);
    
    xercesc::DOMNamedNodeMap* map = surge->getAttributes();
    
    int id = atoi((char*) map->getNamedItem((XMLCh*) "id")->getNodeValue());
    int bonus = atoi((char*) map->getNamedItem((XMLCh*) "value")->getNodeValue());
    
    ((PC*) &*init->lookup(id))->surge(bonus);
}

void process_download(std::shared_ptr<InitTracker> init, Message* xml) {
    xercesc::DOMNode* base = xml->contents()->getFirstChild();
    rotate(base);
    
    xercesc::DOMNamedNodeMap* map = base->getAttributes();
    
    std::string eventID((char*) map->getNamedItem((XMLCh*) "id")->getNodeValue());
    int turnNum = atoi((char*) map->getNamedItem((XMLCh*) "id")->getNodeValue());
    int subTurn = atoi((char*) map->getNamedItem((XMLCh*) "id")->getNodeValue());
    
    init->initialize(eventID, turnNum, subTurn);
    
    xercesc::DOMNode* point = base->getFirstChild();
    xercesc::DOMNode* end = base->getLastChild();
        
    for (; point <= end; point++) {
        rotate(point);
        init->decode(point);
    }
}

void rotate(xercesc::DOMNode*& node) {
    while (node->getNodeType() != ELEMENT_NODE)
        node = node->getNextSibling();
}

#pragma mark Message class methods

std::string Message::requestHeader() {
    std::stringstream ss;
    boost::uuids::random_generator gen;
    boost::uuids::uuid u = gen();
    ss << u;
    return "<request id='" + ss.str() + "'>";
}

std::string Message::responseHeader(const std::string req_ID) {
    return "<response id='" + req_ID + "' success='true'>";
}

std::string Message::responseHeader(const std::string req_ID, 
                                    const std::string reason) {
    return "<response id='" + req_ID + "' success='false' reason='" 
    + reason + "'>";
}

std::string Message::id() {
    return (char*) xmlDoc->getAttributes()->getNamedItem((XMLCh*)"id")
            ->getNodeValue();
}

std::string Message::echo() {
    return std::regex_replace(pure, ((pure.find("</request>") != -1) ? _request : _response), (std::string) "");
}

#pragma mark Server_XIO class methods

template <class D>
void Server_XIO<D>::init() {
    this->emplace("turnRequest", process_changeTurn);
    this->emplace("surgeRequest", process_healingSurge);
    this->emplace("newRequest", process_createUnit);
    this->emplace("killRequest", process_killUnit);
    this->emplace("killAllRequest", process_killAllUnits);
    this->emplace("delayRequest", process_delayTurn);
    this->emplace("rollRequest", process_rollInitiative);
    this->emplace("nullRequest", process_nullInitiative);
}

template <class D>
std::string Server_XIO<D>::process(Server<D>* host, std::shared_ptr<Client_Thread<D>> source, std::string xmlMessage){
    int index = xmlMessage.find("game")+6;
    std::string tracker_ID = xmlMessage.substr(index, index+8);
    InitTracker* init = host->getGame(tracker_ID);
    
    index = xmlMessage.find("id='") + 4;
    std::string id = xmlMessage.substr(index, index+36);
    
    if (init == 0) return Message::responseHeader(id, "This request could not be computed") + success;
    else {
        std::string response = process(init, source, xmlMessage);
        for (Client_Thread<D>* user : host->member_list(tracker_ID))
            send(user, response);
        return Message::responseHeader(id) + success;
    }
}

template <class D>
std::string Server_XIO<D>::process(std::shared_ptr<InitTracker> target, std::shared_ptr<Client_Thread<D>> source,
                         std::string xmlMessage) {
    Message* request = new Message(xmlMessage, XML_IO_Controller<InitTracker*, Client_Thread<D>*, std::string, void, Message*>::handler);
    std::string response = recieve(target, request);
    
    if (response != "")
        send(source, Message::responseHeader(request->id()) + success);
    return response;
}

template <class D>
std::string Server_XIO<D>::recieve(std::shared_ptr<InitTracker> target, Message* xmlMessage){
    
    xercesc::DOMNode* base = xmlMessage->contents()->getFirstChild();
    
    rotate(base);

    std::string type = (char*) base->getLocalName();
    
    std::unordered_map<std::string, void (*)
    (std::shared_ptr<InitTracker>, Message*)>::iterator got;

    got = XML_IO_Controller<std::shared_ptr<InitTracker>, std::shared_ptr<Client_Thread<D>>, std::string, void, Message*>::responseMap.find(type);
    
    if (got == XML_IO_Controller<std::shared_ptr<InitTracker>, std::shared_ptr<Client_Thread<D>>, std::string, void, Message*>::responseMap.end())
        return "";
    else {
        (got->second)(target, xmlMessage);
        return xmlMessage->echo();
    }
}

template <class D>
void Server_XIO<D>::send(std::shared_ptr<Client_Thread<D>> source, std::string xmlMessage){
    source->sendMessage(xmlMessage);
}

#pragma mark Client_XIO class methods

#define ClientControl XML_IO_Controller<std::shared_ptr<InitTracker>, std::shared_ptr<Server_Access>, void, void, Message*>

void Client_XIO::init() {
    this->emplace("turnResponse", process_changeTurn);
    this->emplace("surgeResponse", process_healingSurge);
    this->emplace("newResponse", process_createUnit);
    this->emplace("killResponse", process_killUnit);
    this->emplace("killAllResponse", process_killAllUnits);
    this->emplace("delayResponse", process_delayTurn);
    this->emplace("rollResponse", process_rollInitiative);
    this->emplace("nullResponse", process_nullInitiative);
    this->emplace("initiativeResponse", process_download);    
}

void Client_XIO::process(std::shared_ptr<InitTracker> target, std::shared_ptr<Server_Access> source, std::string xmlMessage) {
    recieve(target, new Message(xmlMessage, handler));
}

void Client_XIO::recieve(std::shared_ptr<InitTracker> target, Message* xmlMessage){
        
    std::string type = (char*) xmlMessage->contents()->getFirstChild()->getLocalName();
    
    std::unordered_map<std::string, void (*)
    (std::shared_ptr<InitTracker>, Message*)>::iterator got;
    
    got = responseMap.find(type);
    
    if (got != responseMap.end())
        (got->second)(target, xmlMessage);
}

void Client_XIO::send(std::shared_ptr<Server_Access> source, std::string xmlMessage){
    source->sendMessage(xmlMessage);
}
